package org.altervista.cp.thinbcgen.bean;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import com.sun.org.apache.bcel.internal.Constants;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Method;
import com.sun.org.apache.bcel.internal.generic.ClassGen;
import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
import com.sun.org.apache.bcel.internal.generic.FieldGen;
import com.sun.org.apache.bcel.internal.generic.InstructionFactory;
import com.sun.org.apache.bcel.internal.generic.InstructionList;
import com.sun.org.apache.bcel.internal.generic.MethodGen;
import com.sun.org.apache.bcel.internal.generic.Type;

@SuppressWarnings("unchecked")
public class SunJavaBeanGenerator extends JavaBeanGenerator implements
		Constants {

	public SunJavaBeanGenerator(Class targetClass, Object[] args) {
		super(targetClass, args);
	}

	protected byte[] generateClass(String generatedClassName, Object[] args) {
		_generatedClassName = generatedClassName;

		_targetJC = Repository.lookupClass(_targetClass.getName());

		String superClassName = _targetJC.isInterface() ? Object.class
				.getName() : _targetJC.getClassName();
		String[] interfaceNames = _targetJC.isInterface() ? new String[] { _targetJC
				.getClassName() }
				: new String[0];

		_cg = new ClassGen(_generatedClassName, superClassName,
				_generatedClassName.substring(_generatedClassName
						.lastIndexOf(".") + 1)
						+ ".java", ACC_PUBLIC | ACC_SUPER, interfaceNames);

		_cp = _cg.getConstantPool();
		_factory = new InstructionFactory(_cg, _cp);

		addConstructors();
		addGettersAndSetters();

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			_cg.getJavaClass().dump(baos);
			java.io.FileOutputStream fos = new java.io.FileOutputStream(
					"/tmp/JavaBean.class");
			_cg.getJavaClass().dump(fos);
			fos.close();
		} catch (IOException e) {
		}
		return baos.toByteArray();

	}

	private void addGettersAndSetters() {
		HashMap fieldGenMap = new HashMap();
		ArrayList targetMethodList = getAbstractMethods(_targetJC);
		for (int i = 0; i < targetMethodList.size(); i++) {
			Method targetMethod = (Method) targetMethodList.get(i);
			boolean getterMethod = targetMethod.getName().startsWith("get");
			boolean setterMethod = targetMethod.getName().startsWith("set");
			if (targetMethod.isAbstract() && getterMethod || setterMethod) {
				MethodGen mg = new MethodGen(targetMethod, _targetJC
						.getClassName(), null);
				if (!((getterMethod && !mg.getReturnType().equals(Type.VOID)) || (setterMethod
						&& mg.getArgumentTypes() != null && mg
						.getArgumentTypes().length == 1))) {
					continue;
				}
				String fieldName = targetMethod.getName().substring(3);
				fieldName = "_" + fieldName.substring(0, 1).toLowerCase()
						+ fieldName.substring(1);
				FieldGen field = (FieldGen) fieldGenMap.get(fieldName);
				if (field == null) {
					Type fieldType = getterMethod ? mg.getReturnType() : mg
							.getArgumentType(0);
					field = new FieldGen(ACC_PRIVATE, fieldType, fieldName, _cp);
					fieldGenMap.put(fieldName, field);
					_cg.addField(field.getField());
				}

				_il = new InstructionList();
				MethodGen method = null;
				if (targetMethod.getName().startsWith("get")) {
					method = new MethodGen(ACC_PUBLIC, field.getType(),
							Type.NO_ARGS, new String[0],
							targetMethod.getName(), _generatedClassName, _il,
							_cp);
					_il.append(InstructionFactory.createLoad(Type.OBJECT, 0));
					_il.append(_factory.createFieldAccess(_generatedClassName,
							fieldName, field.getType(), GETFIELD));
					_il
							.append(InstructionFactory.createReturn(field
									.getType()));
				} else {
					method = new MethodGen(ACC_PUBLIC, Type.VOID,
							new Type[] { field.getType() },
							new String[] { fieldName.substring(1) },
							targetMethod.getName(), _generatedClassName, _il,
							_cp);
					_il.append(InstructionFactory.createLoad(Type.OBJECT, 0));
					_il.append(InstructionFactory
							.createLoad(field.getType(), 1));
					_il.append(_factory.createFieldAccess(_generatedClassName,
							fieldName, field.getType(), PUTFIELD));
					_il.append(InstructionFactory.createReturn(Type.VOID));
				}

				addMethodGen(method);
			}
		}
	}

	private void addConstructors() {
		if (_targetJC.isInterface()) {
			_il = new InstructionList();
			MethodGen method = new MethodGen(ACC_PUBLIC, Type.VOID,
					Type.NO_ARGS, new String[0], "<init>", _generatedClassName,
					_il, _cp);
			_il.append(InstructionFactory.createLoad(Type.OBJECT, 0));
			_il.append(_factory.createInvoke(Object.class.getName(), "<init>",
					Type.VOID, Type.NO_ARGS, INVOKESPECIAL));
			_il.append(InstructionFactory.createReturn(method.getReturnType()));

			addMethodGen(method);
		} else {
			Method[] targetMethods = _targetJC.getMethods();
			for (int i = 0; i < targetMethods.length; i++) {
				if (targetMethods[i].getName().equals("<init>")) {

					Type[] argTypes = Type.getArgumentTypes(targetMethods[i]
							.getSignature());
					Type retType = Type.getReturnType(targetMethods[i]
							.getSignature());

					_il = new InstructionList();
					MethodGen method = new MethodGen(ACC_PUBLIC, retType,
							argTypes, getArgumentNames(argTypes.length),
							targetMethods[i].getName(), _generatedClassName,
							_il, _cp);
					_il.append(InstructionFactory.createLoad(Type.OBJECT, 0));
					int totalSize = 0;
					for (int j = 0; j < argTypes.length; j++) {
						_il.append(InstructionFactory.createLoad(argTypes[j],
								totalSize + 1));
						totalSize += argTypes[j].getSize();
					}
					_il.append(_factory.createInvoke(_targetJC.getClassName(),
							"<init>", method.getReturnType(), method
									.getArgumentTypes(), INVOKESPECIAL));
					_il.append(InstructionFactory.createReturn(method
							.getReturnType()));

					addMethodGen(method);
				}
			}
		}
	}

	private void addMethodGen(MethodGen method) {
		method.setMaxStack();
		method.setMaxLocals();
		_cg.addMethod(method.getMethod());
		_il.dispose();
	}

	private static final ArrayList getAbstractMethods(JavaClass jc) {
		ArrayList methodList = new ArrayList();
		Method[] methods = jc.getMethods();
		for (int i = 0; i < methods.length; i++) {
			if (methods[i].isAbstract()) {
				methodList.add(methods[i]);
			}
		}
		String superClassName = jc.getSuperclassName();
		if (superClassName != Object.class.getName()) {
			methodList.addAll(getAbstractMethods(Repository
					.lookupClass(superClassName)));
		}
		String[] interfaceNames = jc.getInterfaceNames();
		for (int i = 0; i < interfaceNames.length; i++) {
			methodList.addAll(getAbstractMethods(Repository
					.lookupClass(interfaceNames[i])));
		}
		return methodList;
	}

	private static final String[] getArgumentNames(int length) {
		String[] argNames = new String[length];
		for (int i = 0; i < argNames.length; i++) {
			argNames[i] = "arg" + i;
		}
		return argNames;
	}

	private InstructionFactory _factory;
	private ConstantPoolGen _cp;
	private ClassGen _cg;
	private InstructionList _il;

	private JavaClass _targetJC;
	private String _generatedClassName;

}
